#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <stdbool.h>
#include <string.h>

#include "update_image_decode.h"

struct data_frame *df_list = NULL;

void dump_buf(const uint8_t *buf, int ret, const uint8_t *tag)
{
	int i;
	printf("%s\n", tag);
	for (i = 0; i < ret; i += 1) {
		printf("0x%02X ", buf[i]);
		if (!((i + 1) % 8)) {
			printf("\n");
		}
	}
	printf("\n");
}

static int df_list_init(void)
{
	df_list = calloc(1, sizeof(*df_list));
	if (!df_list) {
		printf("%s: Alloc memory failed!\n", __func__);
		return -1;
	}

	df_list->next = df_list;
	df_list->last = df_list;
	df_list->index = 0;
	df_list->buf = NULL;

	return 0;
}

/* merge new data_frame with the last data_frame on the list */
static int df_list_merge_df(struct data_frame *new)
{
	uint32_t last_addr = df_list->last->addr;
	uint16_t last_len = df_list->last->len;
	uint8_t *buf = NULL;

	/* data_frame length is larger than MAX_LEN */
	/* add it to the tail of df_list */
	if (last_len + new->len > DATA_FRAME_MAX_LEN)
		return 1;

	/* data_frame addr is not in the same block of the last one */
	/* add it to the tail of df_list */
	if (last_addr + last_len != new->addr)
		return 1;

	buf = calloc(1, last_len + new->len);
	if (!buf) {
		printf("%s: Alloc memory failed!\n", __func__);
		return -1;
	}

	memcpy(buf, df_list->last->buf, last_len);
	memcpy(buf + last_len, new->buf, new->len);
	df_list->last->len = last_len + new->len;
	free(df_list->last->buf);
	df_list->last->buf = buf;

	free(new->buf);
	free(new);

	return 0;
}

static int df_list_add_tail(struct data_frame *new)
{
	int ret = 0;

	/* First data frame on the list */
	if (df_list->next == df_list) {
		new->index = 1;
		new->next = df_list;
		new->last = df_list;
		df_list->last = new;
		df_list->next = new;
		return ret;
	}

	ret = df_list_merge_df(new);
	/* Merge successfully or alloc memory failed*/
	if (ret <= 0)
		return ret; 

	new->last = df_list->last;
	new->next = df_list;
	df_list->last->next = new;
	df_list->last = new;
	new->index = 1 + new->last->index;
	return ret;
}

static int store_data(uint32_t addr, uint8_t *data, uint16_t len)
{
	struct data_frame *df = NULL;
	int ret = 0;

	if (!data) {
		printf("%s: Data is NULL!\n", __func__);
		return -1;
	}

	df = calloc(1, sizeof(struct data_frame));
	if (!df) {
		printf("%s: Alloc memory failed!\n", __func__);
		return -1;
	}

	df->addr = addr;
	df->len = len;
	df->buf = data;

	ret = df_list_add_tail(df);
	if (ret < 0) {
		free(df);
		return ret;
	}

	return ret;
}

int open_file(char *path, FILE **fp)
{
	*fp = fopen(path, "r");
	if (!fp) {
		printf("%s: Open failed!\n", __func__);
		return -1;
	}

	fseek(*fp, 0, SEEK_END);
	if (ftell(*fp) <= 0) {
		printf("%s: File is empty!\n", __func__);
		fclose(*fp);
		return -1;
	}
	rewind(*fp);

	return 0;
}

static int get_sline_len(const uint8_t *rec, uint16_t rec_size, uint16_t *sline_len)
{
	uint8_t len_str[RECORED_LEN_SIZE + 1] = {0};

	memcpy(len_str, rec + RECORED_LEN_OFFSET, RECORED_LEN_SIZE);
	/* rec_len is num of bytes */
	*sline_len = strtol(len_str, NULL, 16);
	if (*sline_len != GET_SLINE_BYTES(rec_size)) {
		printf("%s:S line length(%d) is invalid!\n",
				__func__, *sline_len);
		return -1;
	}

	return 0;
}

static bool is_addr_valid(uint32_t addr)
{
	return true;
}

static int get_addr(const uint8_t *rec, uint32_t *addr)
{
	uint8_t addr_str[RECORED_ADDR_SIZE + 1] = {0};

	memcpy(addr_str, rec + RECORED_ADDR_OFFSET, RECORED_ADDR_SIZE);
	*addr = (uint32_t)strtol(addr_str, NULL, 16);
	if (!is_addr_valid(*addr)) {
		printf("%s:Date record addr(0x%lX) is invalid!\n",
				__func__, (unsigned long)*addr);
		return -1;
	}

	return 0;
}

static uint8_t is_cs_valid(uint32_t addr)
{
	return true;
}

#if 0
static int check_line_cs(uint8_t *rec, uint16_t rec_size)
{
	uint8_t cs_str[RECORED_CS_SIZE + 1] = {0};
	uint8_t cs;

	memcpy(cs_str, rec + rec_size - 2, RECORED_CS_SIZE);
	cs = (uint8_t)strtol(cs_str, NULL, 16);
	if (!is_cs_valid(cs)) {
		printf("%s:Date record cs(0x%X) is invalid!\n",
				__func__, cs);
		return -1;
	}

	return 0;
}
#endif

static int get_data(const uint8_t *rec, uint16_t rec_size,
			uint8_t **data, uint8_t data_len)
{
	uint8_t buf[3] = {0};
	int i;
	int ret = 0;

	*data = calloc(1, data_len);
	if (!*data) {
		printf("%s: Alloc memory failed!\n", __func__);
		return -1;
	}

	for (i = 0; i < data_len; i++) {
		memcpy(buf, rec + RECORED_DATA_OFFSET + (i << 1), 2);
		(*data)[i] = (uint8_t)strtol(buf, NULL, 16);
	}

#if 0
	/* TODO: Conti dosn't provide checksum per line arithmetic */

	ret = check_line_cs(rec, rec_size);
	if (ret < 0)
		return -1;
#endif

	return 0;
}

static int parse_record(const uint8_t *rec, uint16_t rec_size)
{
	uint16_t sline_len;
	uint32_t addr;
	uint8_t *data;
	uint8_t data_len;
	int ret = 0;

	if (!rec) {
		printf("%s: Record is NULL!\n", __func__);
		return -1;
	}

	switch (rec[RECORED_TYPE_OFFSET + 1]) {
	/* S0 */
	case '0':
		break;
	/* S3 */
	case '3':
		ret = get_sline_len(rec, rec_size, &sline_len);
		if (ret < 0) {
			printf("%s:Get sline length failed!\n", __func__);
			return -1;
		}

		ret = get_addr(rec, &addr);
		if (ret < 0) {
			printf("%s:Get address failed!\n", __func__);
			return -1;
		}

		data_len = GET_DATA_BYTES(sline_len);
		ret = get_data(rec, rec_size, &data, data_len);
		if (ret < 0) {
			printf("%s:Get data failed!\n", __func__);
			return -1;
		}

		ret = store_data(addr, data, data_len);
		if (ret < 0) {
			printf("%s:Store data failed!\n", __func__);
			free(data);
			return -1;
		}

		break;
	/* S7 */
	case '7':
		break;
	default:
		break;
	}

	return ret;
}

#define REC_BUF_LEN 128
static int read_file(FILE *fp)
{
	uint8_t buf[REC_BUF_LEN];
	int ret = 0;

	ret = df_list_init();
	if (ret < 0) {
		printf("%s:data frame list init failed!\n", __func__);
		return ret;
	}

	while (fgets(buf, REC_BUF_LEN, fp)) {
		if (strlen(buf) < 2) {
			printf("%s:Read null line.\n", __func__);
			continue;
		}

		/* remove newline charactor */
		buf[strlen(buf) - 2] = '\0';
		ret = parse_record(buf, strlen(buf));
		if (ret < 0) {
			printf("%s:Parse record error!\n", __func__);
			return ret;
		}
	}

	return ret;
}

static void close_file(FILE *fp)
{
	fclose(fp);
}

static void df_list_print_data(void)
{
	struct data_frame *df = NULL;

	df_list_for_each(df, df_list) {
		printf("df->index:%d\n", df->index);
		printf("df->addr: 0x%08lX\n", (unsigned long)df->addr);
		printf("df->len: %d\n", df->len);
		dump_buf(df->buf, df->len, "datablock");
	}
}

static void df_init(struct data_frame *df)
{
	df->next = df;
	df->last = df;
	free(df->buf);
	free(df);
}

void df_list_free(void)
{
	struct data_frame *cur = NULL;
	struct data_frame *tmp = NULL;

	if(!df_list)
		return;

	df_list_for_each_safe(cur, tmp, df_list) {
		df_list->next = tmp;
		tmp->last = df_list;
		df_init(cur);
	}

	df_init(df_list);
}

int df_list_remove_first(void)
{
	struct data_frame *df = NULL;

	if (df_list->next == df_list)
		return -1; 

	df = df_list->next;
	df_list->next = df->next;
	df->next->last = df_list;
	df_init(df);

	return 0;
}

int load_image(uint8_t *path)
{
	int ret = 0;
	FILE *fp = NULL;

	ret = open_file(path, &fp);
	if (ret < 0)
		return -1;

	ret = read_file(fp);
	if (ret < 0)
		goto fail;

#ifdef DEBUG_PRINT_DATA
	df_list_print_data();
#endif

	close_file(fp);

	return 0;
fail:
	close_file(fp);
	return ret;
}
